namespace OX.Strongbind.Remoting
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Ipc;
    using System.Text;  

    internal class RemotingProxyGenerator : MarshalByRefObject, IProxyGenerator, IDisposable
    {        
        private readonly string channelName;
        private readonly IpcChannel clientChannel;
        private readonly IpcServerChannel serverChannel;
        private readonly Dictionary<string, Guid> roots;
        private readonly Dictionary<string, string> paths;
        private IPropertyAccessNotifier notifier;

        public RemotingProxyGenerator()
        {
            paths = new Dictionary<string, string>();
            roots = new Dictionary<string, Guid>();

            channelName = Guid.NewGuid().ToString();

            IServerChannelSinkProvider provider = new InterceptingServerChannelSinkProvider(this);
            serverChannel = new IpcServerChannel(channelName, channelName, provider);

            ChannelServices.RegisterChannel(serverChannel, true);
            serverChannel.StartListening(null);

            clientChannel = new IpcChannel();
            ChannelServices.RegisterChannel(clientChannel, true);
        }
        
        public T For<T>(Guid sourceIdentifier, IPropertyAccessNotifier notifier)
        {
            if (!typeof(MarshalByRefObject).IsAssignableFrom(typeof(T)))
                throw new InvalidOperationException("Type must inherit MarshalByRefObject");

            if (this.notifier == null)
                this.notifier = notifier;            

            string identifier = Guid.NewGuid().ToString();
            roots.Add(identifier, sourceIdentifier);
            paths.Add(identifier, notifier.GetLastNavigationPath());

            RemotingConfiguration.RegisterWellKnownServiceType(typeof(T), identifier, WellKnownObjectMode.Singleton);

            T remote = (T)Activator.GetObject(typeof(T), string.Format("ipc://{0}/{1}", channelName, identifier));

            return remote;
        }

        public void Dispose()
        {
            ChannelServices.UnregisterChannel(clientChannel);

            serverChannel.StopListening(null);
            ChannelServices.UnregisterChannel(serverChannel);
        }

        internal void SetProperty(string identifier, string property)
        {
            Guid root = roots[identifier];
            notifier.NotifyGetProperty(root, string.IsNullOrEmpty(paths[identifier]) ? property : string.Format("{0}.{1}", paths[identifier], property));
        }
    }
}
